bouquin 0.1.12__py3-none-any.whl → 0.2.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bouquin/db.py +4 -41
- bouquin/find_bar.py +33 -11
- bouquin/history_dialog.py +27 -26
- bouquin/main_window.py +495 -120
- bouquin/markdown_editor.py +813 -0
- bouquin/search.py +46 -30
- bouquin/toolbar.py +0 -42
- {bouquin-0.1.12.dist-info → bouquin-0.2.1.2.dist-info}/METADATA +5 -7
- bouquin-0.2.1.2.dist-info/RECORD +21 -0
- bouquin/editor.py +0 -1009
- bouquin-0.1.12.dist-info/RECORD +0 -21
- {bouquin-0.1.12.dist-info → bouquin-0.2.1.2.dist-info}/LICENSE +0 -0
- {bouquin-0.1.12.dist-info → bouquin-0.2.1.2.dist-info}/WHEEL +0 -0
- {bouquin-0.1.12.dist-info → bouquin-0.2.1.2.dist-info}/entry_points.txt +0 -0
bouquin/db.py
CHANGED
|
@@ -3,10 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import csv
|
|
4
4
|
import html
|
|
5
5
|
import json
|
|
6
|
-
import os
|
|
7
6
|
|
|
8
7
|
from dataclasses import dataclass
|
|
9
|
-
from markdownify import markdownify as md
|
|
10
8
|
from pathlib import Path
|
|
11
9
|
from sqlcipher3 import dbapi2 as sqlite
|
|
12
10
|
from typing import List, Sequence, Tuple
|
|
@@ -401,25 +399,13 @@ class DBManager:
|
|
|
401
399
|
Export to HTML, similar to export_html, but then convert to Markdown
|
|
402
400
|
using markdownify, and finally save to file.
|
|
403
401
|
"""
|
|
404
|
-
parts = [
|
|
405
|
-
"<!doctype html>",
|
|
406
|
-
'<html lang="en">',
|
|
407
|
-
"<body>",
|
|
408
|
-
f"<h1>{html.escape(title)}</h1>",
|
|
409
|
-
]
|
|
402
|
+
parts = []
|
|
410
403
|
for d, c in entries:
|
|
411
|
-
parts.append(
|
|
412
|
-
|
|
413
|
-
)
|
|
414
|
-
parts.append("</body></html>")
|
|
415
|
-
|
|
416
|
-
# Convert html to markdown
|
|
417
|
-
md_items = []
|
|
418
|
-
for item in parts:
|
|
419
|
-
md_items.append(md(item, heading_style="ATX"))
|
|
404
|
+
parts.append(f"# {d}")
|
|
405
|
+
parts.append(c)
|
|
420
406
|
|
|
421
407
|
with open(file_path, "w", encoding="utf-8") as f:
|
|
422
|
-
f.write("\n".join(
|
|
408
|
+
f.write("\n".join(parts))
|
|
423
409
|
|
|
424
410
|
def export_sql(self, file_path: str) -> None:
|
|
425
411
|
"""
|
|
@@ -440,29 +426,6 @@ class DBManager:
|
|
|
440
426
|
cur.execute("SELECT sqlcipher_export('backup')")
|
|
441
427
|
cur.execute("DETACH DATABASE backup")
|
|
442
428
|
|
|
443
|
-
def export_by_extension(self, file_path: str) -> None:
|
|
444
|
-
"""
|
|
445
|
-
Fallback catch-all that runs one of the above functions based on
|
|
446
|
-
the extension of the file name that was chosen by the user.
|
|
447
|
-
"""
|
|
448
|
-
entries = self.get_all_entries()
|
|
449
|
-
ext = os.path.splitext(file_path)[1].lower()
|
|
450
|
-
|
|
451
|
-
if ext == ".json":
|
|
452
|
-
self.export_json(entries, file_path)
|
|
453
|
-
elif ext == ".csv":
|
|
454
|
-
self.export_csv(entries, file_path)
|
|
455
|
-
elif ext == ".txt":
|
|
456
|
-
self.export_txt(entries, file_path)
|
|
457
|
-
elif ext in {".html", ".htm"}:
|
|
458
|
-
self.export_html(entries, file_path)
|
|
459
|
-
elif ext in {".sql", ".sqlite"}:
|
|
460
|
-
self.export_sql(file_path)
|
|
461
|
-
elif ext == ".md":
|
|
462
|
-
self.export_markdown(entries, file_path)
|
|
463
|
-
else:
|
|
464
|
-
raise ValueError(f"Unsupported extension: {ext}")
|
|
465
|
-
|
|
466
429
|
def compact(self) -> None:
|
|
467
430
|
"""
|
|
468
431
|
Runs VACUUM on the db.
|
bouquin/find_bar.py
CHANGED
|
@@ -21,9 +21,8 @@ from PySide6.QtWidgets import (
|
|
|
21
21
|
class FindBar(QWidget):
|
|
22
22
|
"""Widget for finding text in the Editor"""
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
) # emitted when the bar is hidden (Esc/✕), so caller can refocus editor
|
|
24
|
+
# emitted when the bar is hidden (Esc/✕), so caller can refocus editor
|
|
25
|
+
closed = Signal()
|
|
27
26
|
|
|
28
27
|
def __init__(
|
|
29
28
|
self,
|
|
@@ -31,16 +30,21 @@ class FindBar(QWidget):
|
|
|
31
30
|
shortcut_parent: QWidget | None = None,
|
|
32
31
|
parent: QWidget | None = None,
|
|
33
32
|
):
|
|
33
|
+
|
|
34
34
|
super().__init__(parent)
|
|
35
|
-
self.editor = editor
|
|
36
35
|
|
|
37
|
-
#
|
|
36
|
+
# store how to get the current editor
|
|
37
|
+
self._editor_getter = editor if callable(editor) else (lambda: editor)
|
|
38
|
+
self.shortcut_parent = shortcut_parent
|
|
39
|
+
|
|
40
|
+
# UI (build ONCE)
|
|
38
41
|
layout = QHBoxLayout(self)
|
|
39
42
|
layout.setContentsMargins(6, 0, 6, 0)
|
|
40
43
|
|
|
41
44
|
layout.addWidget(QLabel("Find:"))
|
|
45
|
+
|
|
42
46
|
self.edit = QLineEdit(self)
|
|
43
|
-
self.edit.setPlaceholderText("Type to search
|
|
47
|
+
self.edit.setPlaceholderText("Type to search")
|
|
44
48
|
layout.addWidget(self.edit)
|
|
45
49
|
|
|
46
50
|
self.case = QCheckBox("Match case", self)
|
|
@@ -56,11 +60,15 @@ class FindBar(QWidget):
|
|
|
56
60
|
|
|
57
61
|
self.setVisible(False)
|
|
58
62
|
|
|
59
|
-
# Shortcut
|
|
60
|
-
sp =
|
|
61
|
-
|
|
63
|
+
# Shortcut (press Esc to hide bar)
|
|
64
|
+
sp = (
|
|
65
|
+
self.shortcut_parent
|
|
66
|
+
if self.shortcut_parent is not None
|
|
67
|
+
else (self.parent() or self)
|
|
68
|
+
)
|
|
69
|
+
QShortcut(Qt.Key_Escape, sp, activated=self._maybe_hide)
|
|
62
70
|
|
|
63
|
-
# Signals
|
|
71
|
+
# Signals (connect ONCE)
|
|
64
72
|
self.edit.returnPressed.connect(self.find_next)
|
|
65
73
|
self.edit.textChanged.connect(self._update_highlight)
|
|
66
74
|
self.case.toggled.connect(self._update_highlight)
|
|
@@ -68,10 +76,17 @@ class FindBar(QWidget):
|
|
|
68
76
|
self.prevBtn.clicked.connect(self.find_prev)
|
|
69
77
|
self.closeBtn.clicked.connect(self.hide_bar)
|
|
70
78
|
|
|
79
|
+
@property
|
|
80
|
+
def editor(self) -> QTextEdit | None:
|
|
81
|
+
"""Get the current editor"""
|
|
82
|
+
return self._editor_getter()
|
|
83
|
+
|
|
71
84
|
# ----- Public API -----
|
|
72
85
|
|
|
73
86
|
def show_bar(self):
|
|
74
87
|
"""Show the bar, seed with current selection if sensible, focus the line edit."""
|
|
88
|
+
if not self.editor:
|
|
89
|
+
return
|
|
75
90
|
tc = self.editor.textCursor()
|
|
76
91
|
sel = tc.selectedText().strip()
|
|
77
92
|
if sel and "\u2029" not in sel: # ignore multi-paragraph selections
|
|
@@ -105,6 +120,8 @@ class FindBar(QWidget):
|
|
|
105
120
|
return flags
|
|
106
121
|
|
|
107
122
|
def find_next(self):
|
|
123
|
+
if not self.editor:
|
|
124
|
+
return
|
|
108
125
|
txt = self.edit.text()
|
|
109
126
|
if not txt:
|
|
110
127
|
return
|
|
@@ -130,6 +147,8 @@ class FindBar(QWidget):
|
|
|
130
147
|
self._update_highlight()
|
|
131
148
|
|
|
132
149
|
def find_prev(self):
|
|
150
|
+
if not self.editor:
|
|
151
|
+
return
|
|
133
152
|
txt = self.edit.text()
|
|
134
153
|
if not txt:
|
|
135
154
|
return
|
|
@@ -155,6 +174,8 @@ class FindBar(QWidget):
|
|
|
155
174
|
self._update_highlight()
|
|
156
175
|
|
|
157
176
|
def _update_highlight(self):
|
|
177
|
+
if not self.editor:
|
|
178
|
+
return
|
|
158
179
|
txt = self.edit.text()
|
|
159
180
|
if not txt:
|
|
160
181
|
self._clear_highlight()
|
|
@@ -183,4 +204,5 @@ class FindBar(QWidget):
|
|
|
183
204
|
self.editor.setExtraSelections(selections)
|
|
184
205
|
|
|
185
206
|
def _clear_highlight(self):
|
|
186
|
-
self.editor
|
|
207
|
+
if self.editor:
|
|
208
|
+
self.editor.setExtraSelections([])
|
bouquin/history_dialog.py
CHANGED
|
@@ -16,31 +16,33 @@ from PySide6.QtWidgets import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
s =
|
|
30
|
-
|
|
31
|
-
s =
|
|
32
|
-
|
|
33
|
-
s =
|
|
34
|
-
|
|
35
|
-
s =
|
|
36
|
-
s =
|
|
19
|
+
def _markdown_to_text(s: str) -> str:
|
|
20
|
+
"""Convert markdown to plain text for diff comparison."""
|
|
21
|
+
# Remove images
|
|
22
|
+
s = re.sub(r"!\[.*?\]\(.*?\)", "[ Image ]", s)
|
|
23
|
+
# Remove inline code formatting
|
|
24
|
+
s = re.sub(r"`([^`]+)`", r"\1", s)
|
|
25
|
+
# Remove bold/italic markers
|
|
26
|
+
s = re.sub(r"\*\*([^*]+)\*\*", r"\1", s)
|
|
27
|
+
s = re.sub(r"__([^_]+)__", r"\1", s)
|
|
28
|
+
s = re.sub(r"\*([^*]+)\*", r"\1", s)
|
|
29
|
+
s = re.sub(r"_([^_]+)_", r"\1", s)
|
|
30
|
+
# Remove strikethrough
|
|
31
|
+
s = re.sub(r"~~([^~]+)~~", r"\1", s)
|
|
32
|
+
# Remove heading markers
|
|
33
|
+
s = re.sub(r"^#{1,6}\s+", "", s, flags=re.MULTILINE)
|
|
34
|
+
# Remove list markers
|
|
35
|
+
s = re.sub(r"^\s*[-*+]\s+", "", s, flags=re.MULTILINE)
|
|
36
|
+
s = re.sub(r"^\s*\d+\.\s+", "", s, flags=re.MULTILINE)
|
|
37
|
+
# Remove checkbox markers
|
|
38
|
+
s = re.sub(r"^\s*-\s*\[[x ☐☑]\]\s+", "", s, flags=re.MULTILINE)
|
|
37
39
|
return s.strip()
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
def _colored_unified_diff_html(
|
|
42
|
+
def _colored_unified_diff_html(old_md: str, new_md: str) -> str:
|
|
41
43
|
"""Return HTML with colored unified diff (+ green, - red, context gray)."""
|
|
42
|
-
a =
|
|
43
|
-
b =
|
|
44
|
+
a = _markdown_to_text(old_md).splitlines()
|
|
45
|
+
b = _markdown_to_text(new_md).splitlines()
|
|
44
46
|
ud = difflib.unified_diff(a, b, fromfile="current", tofile="selected", lineterm="")
|
|
45
47
|
lines = []
|
|
46
48
|
for line in ud:
|
|
@@ -116,9 +118,9 @@ class HistoryDialog(QDialog):
|
|
|
116
118
|
return local.strftime("%Y-%m-%d %H:%M:%S %Z")
|
|
117
119
|
|
|
118
120
|
def _load_versions(self):
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
# [{id,version_no,created_at,note,is_current}]
|
|
122
|
+
self._versions = self._db.list_versions(self._date)
|
|
123
|
+
|
|
122
124
|
self._current_id = next(
|
|
123
125
|
(v["id"] for v in self._versions if v["is_current"]), None
|
|
124
126
|
)
|
|
@@ -150,9 +152,8 @@ class HistoryDialog(QDialog):
|
|
|
150
152
|
self.btn_revert.setEnabled(False)
|
|
151
153
|
return
|
|
152
154
|
sel_id = item.data(Qt.UserRole)
|
|
153
|
-
# Preview selected as HTML
|
|
154
155
|
sel = self._db.get_version(version_id=sel_id)
|
|
155
|
-
self.preview.
|
|
156
|
+
self.preview.setMarkdown(sel["content"])
|
|
156
157
|
# Diff vs current (textual diff)
|
|
157
158
|
cur = self._db.get_version(version_id=self._current_id)
|
|
158
159
|
self.diff.setHtml(_colored_unified_diff_html(cur["content"], sel["content"]))
|